dojo.provide("miaigo.model.sgf.SGFGameReader");

dojo.require("miaigo.model.GameReader");
dojo.require("miaigo.model.sgf.SGFStateMachine");
dojo.require("miaigo.model.sgf.PropertyFactory");
dojo.require("miaigo.model.sgf.SGFNode");
dojo.require("miaigo.model.sgf.SGFTree");
dojo.require("miaigo.model.sgf.SGFGame");

dojo.declare(
    "miaigo.model.sgf.SGFGameReader",
    [miaigo.model.GameReader],
    {
	// a SGF state machine
	_stateMachine: null,

	// games read
	_games: null,

	// temp states
	_currentTree: null,
	_currentNode: null,
	_currentProperty: null,

	constructor: function(readerParam)
	{
	    this._stateMachine = new miaigo.model.sgf.SGFStateMachine();
	    this._games = [];
	},

	reset: function()
	{
	    this._currentTree = null;
	    this._currentNode = null;
	    this._currentProperty = null;
	    this._games = [];
	    this._error = null;
	    this._stateMachine.initialize();
	},

	// Override miaigo.model.GameReader.
	// Return: An array of miaigo.model.sgf.SGFGame
	readGame: function(/* String */gameString)
	{
	    // initialize state machine
	    this.reset();

	    // set connections between reader and state machine
	    dojo.connect(this._stateMachine, "onTreeStarted", this, "_onTree");
	    dojo.connect(this._stateMachine, "onTreeEnded", this, "_onTreeClosed");
	    dojo.connect(this._stateMachine, "onNodeStarted", this, "_onNode");
	    dojo.connect(this._stateMachine, "onProp", this, "_onProp");
	    dojo.connect(this._stateMachine, "onValue", this, "_onValue");

	    for (var i = 0; i < gameString.length; i++)
	    {
		var ch = gameString.charAt(i);
		this._stateMachine.inputChar(ch);

		// check error
		if (this._error)
		{
		    if (this.onError)
		    {
			this.onError(this._error);
		    }
		    break;
		}

		// update progress
		if (this.onProgress)
		{
		    this.onProgress(i + "/" + gameString.length);
		}
	    }

	    return this._games;
	},

	asyncReadGame: function(/*String*/gameString)
	{
	    // initialize state machine
	    this.reset();

	    // set connections between reader and state machine
	    dojo.connect(this._stateMachine, "onTreeStarted", this, "_onTree");
	    dojo.connect(this._stateMachine, "onTreeEnded", this, "_onTreeClosed");
	    dojo.connect(this._stateMachine, "onNodeStarted", this, "_onNode");
	    dojo.connect(this._stateMachine, "onProp", this, "_onProp");
	    dojo.connect(this._stateMachine, "onValue", this, "_onValue");

	    var iterationSize = 1000;
	    var i = 0;
	    var busy = false;

	    var processor = setInterval(
		dojo.hitch(this, function()
		{
		    if (!busy)
		    {
			busy = true;

			for (; i<gameString.length; i++)
			{
			    var ch = gameString.charAt(i);
			    this._stateMachine.inputChar(ch);

			    // check error
			    if (this._error)
			    {
				if (this.onError)
				{
				    this.onError(this._error);
				}
				break;
			    }

			    if (i >= iterationSize)
			    {
				iterationSize += 1000;
				i++;
				break;
			    }
			}

			if (this._error)
			{
			    clearInterval(processor);
			}

			if (i >= gameString.length)
			{
			    clearInterval(processor);
			    this.onLoad(this._games);
			}

			// update progress
			if (this.onProgress)
			{
			    this.onProgress(i + "/" + gameString.length);
			}

			busy = false;
		    }
		}),
		80
	    );

	},

	_onTree: function()
	{
	    // create a new tree
	    var tree = new miaigo.model.sgf.SGFTree({owner: this._currentNode});

	    if (!this._currentNode)
	    {
		// create a game for root tree
		var game = new miaigo.model.sgf.SGFGame({tree: tree});
		this._games.push(game);
	    }
	    else
	    {
		// An alternation tree
		// Add it to current node as an alternation
		this._currentNode.addVariation(tree);

		// close current property
		if (this._currentProperty)
		{
		    this._currentNode.addProperty(this._currentProperty);
		    this._currentProperty = null;
		}
	    }

	    // Followed nodes will be appended on this tree
	    this._currentTree = tree;

	    // A tree start without a node
	    this._currentNode = null;
	},

	_onTreeClosed: function()
	{
	    // close last property
	    if (this._currentProperty && this._currentNode)
	    {
		this._currentNode.addProperty(this._currentProperty);
		this._currentProperty = null;
	    }

	    // close last node
	    if (this._currentNode && this._currentTree)
	    {
		this._currentTree.addNode(this._currentNode);
		this._currentNode = null;
	    }

	    // close current tree, and restore it as it's parent tree
	    if (this._currentTree)
	    {
		this._currentNode = this._currentTree.getOwner();
		this._currentTree = this._currentTree.getParent();
	    }
	    else
	    {
		this.reportError("Closing a null tree!");
	    }
	},

	_onNode: function()
	{
	    // close current property
	    if (this._currentProperty && this._currentNode)
	    {
		this._currentNode.addProperty(this._currentProperty);
		this._currentProperty = null;
	    }

	    // close current node
	    if (this._currentNode)
	    {
		if (this._currentTree)
		{
		    this._currentTree.addNode(this._currentNode);
		}
		else
		{
		    this.reportError("Orphan node");
		}
	    }

	    // create a new node
	    var isRootNode = (!this._currentTree.getOwner()) && 
		(this._currentTree.getNodeCount() == 0);
	    this._currentNode = new miaigo.model.sgf.SGFNode
	    (
		{
		    parent: this._currentTree,
		    isRoot: isRootNode
		}
	    );
	},

	_onProp: function(propID)
	{
	    // close current property
	    if (this._currentNode)
	    {
		this._currentNode.addProperty(this._currentProperty);
	    }
	    else
	    {
		this.reportError("Adding property without a node!");
	    }

	    // create a new property
	    this._currentProperty = miaigo.model.sgf.PropertyFactory.createProperty(propID);
	},

	_onValue: function(value)
	{
	    if (this._currentProperty)
	    {
		this._currentProperty.addValue(value);
	    }
	    else
	    {
		this.reportError("Adding value without a property!");
	    }
	}
    });